#include <QtTest/QtTest>
#include "protocolhandler.h"

class TestProtocolHandler : public QObject
{
    Q_OBJECT

private slots:
    void initTestCase();
    void cleanupTestCase();
    void init();
    void cleanup();

    // Frame construction tests
    void testBuildCommandFrame_ManualFocusPlus();
    void testBuildCommandFrame_SetFocusValue();
    void testBuildCommandFrame_NoParameters();
    void testBuildCommandFrame_FrameCounterIncrement();
    
    // Checksum tests
    void testCalculateChecksum_Simple();
    void testCalculateChecksum_Overflow();
    
    // Frame parsing tests
    void testProcessIncomingData_ValidFrame();
    void testProcessIncomingData_InvalidChecksum();
    void testProcessIncomingData_IncompleteFrame();
    void testProcessIncomingData_MultipleFrames();
    void testProcessIncomingData_NoStartMarker();
    
    // Little-endian byte ordering tests
    void testLittleEndian_StartMarker();
    void testLittleEndian_StatusValues();

private:
    ProtocolHandler *m_handler;
    
    // Helper to create a valid status frame
    QByteArray createStatusFrame(quint8 sourceId, quint8 destId, quint8 frameCounter,
                                  quint8 command, quint8 workStatus,
                                  qint16 currentStep, qint16 totalTravel);
};

void TestProtocolHandler::initTestCase()
{
    // Called once before all tests
}

void TestProtocolHandler::cleanupTestCase()
{
    // Called once after all tests
}

void TestProtocolHandler::init()
{
    // Called before each test
    m_handler = new ProtocolHandler();
}

void TestProtocolHandler::cleanup()
{
    // Called after each test
    delete m_handler;
    m_handler = nullptr;
}

// Test frame construction with ManualFocusPlus command (1 parameter)
void TestProtocolHandler::testBuildCommandFrame_ManualFocusPlus()
{
    quint8 sourceId = 0x01;
    quint8 destId = 0x02;
    quint8 frameCounter = 0x00;
    quint8 command = static_cast<quint8>(FocusCommand::ManualFocusPlus);
    quint8 stepLevel = 128;
    
    QByteArray params;
    params.append(static_cast<char>(stepLevel));
    
    QByteArray frame = m_handler->buildCommandFrame(sourceId, destId, frameCounter, command, params);
    
    // Verify frame structure
    QVERIFY(frame.size() == 10);  // 2+1+1+1+1+1+1+1+1 = 10 bytes
    
    // Verify start marker (0xAAAA in little-endian: 0xAA, 0xAA)
    QCOMPARE(static_cast<quint8>(frame[0]), static_cast<quint8>(0xAA));
    QCOMPARE(static_cast<quint8>(frame[1]), static_cast<quint8>(0xAA));
    
    // Verify frame length (total bytes - 1 = 9)
    QCOMPARE(static_cast<quint8>(frame[2]), static_cast<quint8>(9));
    
    // Verify source ID
    QCOMPARE(static_cast<quint8>(frame[3]), sourceId);
    
    // Verify destination ID
    QCOMPARE(static_cast<quint8>(frame[4]), destId);
    
    // Verify frame ID (0x00)
    QCOMPARE(static_cast<quint8>(frame[5]), static_cast<quint8>(0x00));
    
    // Verify frame counter
    QCOMPARE(static_cast<quint8>(frame[6]), frameCounter);
    
    // Verify command
    QCOMPARE(static_cast<quint8>(frame[7]), command);
    
    // Verify parameter
    QCOMPARE(static_cast<quint8>(frame[8]), stepLevel);
    
    // Verify checksum (last byte)
    quint32 sum = 0;
    for (int i = 0; i < frame.size() - 1; ++i) {
        sum += static_cast<quint8>(frame[i]);
    }
    QCOMPARE(static_cast<quint8>(frame[9]), static_cast<quint8>(sum & 0xFF));
}

// Test frame construction with SetFocusValue command (2-byte parameter)
void TestProtocolHandler::testBuildCommandFrame_SetFocusValue()
{
    quint8 sourceId = 0x01;
    quint8 destId = 0x0D;
    quint8 frameCounter = 0x05;
    quint8 command = static_cast<quint8>(FocusCommand::SetFocusValue);
    quint16 focusValue = 3000;
    
    QByteArray params;
    QDataStream stream(&params, QIODevice::WriteOnly);
    stream.setByteOrder(QDataStream::LittleEndian);
    stream << focusValue;
    
    QByteArray frame = m_handler->buildCommandFrame(sourceId, destId, frameCounter, command, params);
    
    // Verify frame size (2+1+1+1+1+1+1+2+1 = 11 bytes)
    QCOMPARE(frame.size(), 11);
    
    // Verify frame length
    QCOMPARE(static_cast<quint8>(frame[2]), static_cast<quint8>(10));
    
    // Verify command
    QCOMPARE(static_cast<quint8>(frame[7]), command);
    
    // Verify parameter (little-endian: 3000 = 0x0BB8 -> 0xB8, 0x0B)
    QCOMPARE(static_cast<quint8>(frame[8]), static_cast<quint8>(0xB8));
    QCOMPARE(static_cast<quint8>(frame[9]), static_cast<quint8>(0x0B));
}

// Test frame construction with no parameters (Brake command)
void TestProtocolHandler::testBuildCommandFrame_NoParameters()
{
    quint8 sourceId = 0x01;
    quint8 destId = 0x02;
    quint8 frameCounter = 0x10;
    quint8 command = static_cast<quint8>(FocusCommand::Brake);
    QByteArray params;  // Empty
    
    QByteArray frame = m_handler->buildCommandFrame(sourceId, destId, frameCounter, command, params);
    
    // Verify frame size (2+1+1+1+1+1+1+1 = 9 bytes)
    QCOMPARE(frame.size(), 9);
    
    // Verify frame length
    QCOMPARE(static_cast<quint8>(frame[2]), static_cast<quint8>(8));
    
    // Verify command
    QCOMPARE(static_cast<quint8>(frame[7]), command);
}

// Test frame counter in different frames
void TestProtocolHandler::testBuildCommandFrame_FrameCounterIncrement()
{
    quint8 sourceId = 0x01;
    quint8 destId = 0x02;
    quint8 command = static_cast<quint8>(FocusCommand::Reset);
    QByteArray params;
    
    // Build frames with different counters
    QByteArray frame1 = m_handler->buildCommandFrame(sourceId, destId, 0, command, params);
    QByteArray frame2 = m_handler->buildCommandFrame(sourceId, destId, 1, command, params);
    QByteArray frame3 = m_handler->buildCommandFrame(sourceId, destId, 255, command, params);
    
    // Verify frame counters
    QCOMPARE(static_cast<quint8>(frame1[6]), static_cast<quint8>(0));
    QCOMPARE(static_cast<quint8>(frame2[6]), static_cast<quint8>(1));
    QCOMPARE(static_cast<quint8>(frame3[6]), static_cast<quint8>(255));
}

// Test checksum calculation with simple data
void TestProtocolHandler::testCalculateChecksum_Simple()
{
    QByteArray data;
    data.append(static_cast<char>(0x01));
    data.append(static_cast<char>(0x02));
    data.append(static_cast<char>(0x03));
    
    // Create a temporary handler to access calculateChecksum
    // Since it's private, we'll test it indirectly through frame construction
    quint8 sourceId = 0x01;
    quint8 destId = 0x02;
    quint8 frameCounter = 0x00;
    quint8 command = 0x01;
    QByteArray params;
    
    QByteArray frame = m_handler->buildCommandFrame(sourceId, destId, frameCounter, command, params);
    
    // Calculate expected checksum manually
    quint32 sum = 0;
    for (int i = 0; i < frame.size() - 1; ++i) {
        sum += static_cast<quint8>(frame[i]);
    }
    quint8 expectedChecksum = static_cast<quint8>(sum & 0xFF);
    
    // Verify checksum in frame
    QCOMPARE(static_cast<quint8>(frame[frame.size() - 1]), expectedChecksum);
}

// Test checksum calculation with overflow (sum > 255)
void TestProtocolHandler::testCalculateChecksum_Overflow()
{
    // Create a frame that will cause checksum overflow
    quint8 sourceId = 0xFF;
    quint8 destId = 0xFF;
    quint8 frameCounter = 0xFF;
    quint8 command = 0xFF;
    QByteArray params;
    params.append(static_cast<char>(0xFF));
    
    QByteArray frame = m_handler->buildCommandFrame(sourceId, destId, frameCounter, command, params);
    
    // Calculate expected checksum (should only use low 8 bits)
    quint32 sum = 0;
    for (int i = 0; i < frame.size() - 1; ++i) {
        sum += static_cast<quint8>(frame[i]);
    }
    quint8 expectedChecksum = static_cast<quint8>(sum & 0xFF);
    
    // Verify checksum uses only low 8 bits
    QCOMPARE(static_cast<quint8>(frame[frame.size() - 1]), expectedChecksum);
    QVERIFY(sum > 255);  // Verify we actually had overflow
}

// Helper function to create a valid status frame
QByteArray TestProtocolHandler::createStatusFrame(quint8 sourceId, quint8 destId, quint8 frameCounter,
                                                   quint8 command, quint8 workStatus,
                                                   qint16 currentStep, qint16 totalTravel)
{
    QByteArray frame;
    QDataStream stream(&frame, QIODevice::WriteOnly);
    stream.setByteOrder(QDataStream::LittleEndian);
    
    // Write start marker
    stream << FRAME_START_MARKER;
    
    // Write frame length (13 - 1 = 12)
    stream << static_cast<quint8>(12);
    
    // Write IDs and counters
    stream << sourceId;
    stream << destId;
    stream << static_cast<quint8>(0x00);  // Frame ID
    stream << frameCounter;
    
    // Write command and status
    stream << command;
    stream << workStatus;
    stream << currentStep;
    stream << totalTravel;
    
    // Calculate and append checksum
    quint32 sum = 0;
    for (int i = 0; i < frame.size(); ++i) {
        sum += static_cast<quint8>(frame[i]);
    }
    stream << static_cast<quint8>(sum & 0xFF);
    
    return frame;
}

// Test processing a valid incoming frame
void TestProtocolHandler::testProcessIncomingData_ValidFrame()
{
    QSignalSpy spy(m_handler, &ProtocolHandler::frameReceived);
    
    quint8 sourceId = 0x02;
    quint8 destId = 0x01;
    quint8 frameCounter = 0x00;
    quint8 command = 0x01;
    quint8 workStatus = 0x03;  // Bits 0 and 1 set
    qint16 currentStep = 1500;
    qint16 totalTravel = 6000;
    
    QByteArray frame = createStatusFrame(sourceId, destId, frameCounter, command, workStatus,
                                          currentStep, totalTravel);
    
    m_handler->processIncomingData(frame);
    
    // Verify signal was emitted
    QCOMPARE(spy.count(), 1);
    
    // Verify signal parameters
    QList<QVariant> arguments = spy.takeFirst();
    QCOMPARE(arguments.at(0).value<quint8>(), sourceId);
    QCOMPARE(arguments.at(1).value<quint8>(), command);
    QCOMPARE(arguments.at(2).value<quint8>(), workStatus);
    QCOMPARE(arguments.at(3).value<qint16>(), currentStep);
    QCOMPARE(arguments.at(4).value<qint16>(), totalTravel);
}

// Test processing a frame with invalid checksum
void TestProtocolHandler::testProcessIncomingData_InvalidChecksum()
{
    QSignalSpy receivedSpy(m_handler, &ProtocolHandler::frameReceived);
    QSignalSpy errorSpy(m_handler, &ProtocolHandler::frameError);
    
    QByteArray frame = createStatusFrame(0x02, 0x01, 0x00, 0x01, 0x03, 1500, 6000);
    
    // Corrupt the checksum
    frame[frame.size() - 1] = static_cast<char>(0xFF);
    
    m_handler->processIncomingData(frame);
    
    // Verify no frameReceived signal
    QCOMPARE(receivedSpy.count(), 0);
    
    // Verify frameError signal was emitted
    QCOMPARE(errorSpy.count(), 1);
}

// Test processing incomplete frame (should wait for more data)
void TestProtocolHandler::testProcessIncomingData_IncompleteFrame()
{
    QSignalSpy spy(m_handler, &ProtocolHandler::frameReceived);
    
    QByteArray completeFrame = createStatusFrame(0x02, 0x01, 0x00, 0x01, 0x03, 1500, 6000);
    
    // Send only first 5 bytes
    QByteArray partialFrame = completeFrame.left(5);
    m_handler->processIncomingData(partialFrame);
    
    // Verify no signal emitted yet
    QCOMPARE(spy.count(), 0);
    
    // Send remaining bytes
    QByteArray remainingFrame = completeFrame.mid(5);
    m_handler->processIncomingData(remainingFrame);
    
    // Now signal should be emitted
    QCOMPARE(spy.count(), 1);
}

// Test processing multiple frames in one data chunk
void TestProtocolHandler::testProcessIncomingData_MultipleFrames()
{
    QSignalSpy spy(m_handler, &ProtocolHandler::frameReceived);
    
    QByteArray frame1 = createStatusFrame(0x02, 0x01, 0x00, 0x01, 0x03, 1500, 6000);
    QByteArray frame2 = createStatusFrame(0x02, 0x01, 0x01, 0x02, 0x01, 2000, 6000);
    
    // Combine both frames
    QByteArray combinedData = frame1 + frame2;
    
    m_handler->processIncomingData(combinedData);
    
    // Verify both frames were processed
    QCOMPARE(spy.count(), 2);
}

// Test processing data with no start marker
void TestProtocolHandler::testProcessIncomingData_NoStartMarker()
{
    QSignalSpy receivedSpy(m_handler, &ProtocolHandler::frameReceived);
    QSignalSpy errorSpy(m_handler, &ProtocolHandler::frameError);
    
    // Send garbage data without start marker
    QByteArray garbageData;
    garbageData.append(static_cast<char>(0x01));
    garbageData.append(static_cast<char>(0x02));
    garbageData.append(static_cast<char>(0x03));
    
    m_handler->processIncomingData(garbageData);
    
    // Verify no frameReceived signal
    QCOMPARE(receivedSpy.count(), 0);
    
    // Verify frameError signal was emitted
    QCOMPARE(errorSpy.count(), 1);
}

// Test little-endian byte ordering for start marker
void TestProtocolHandler::testLittleEndian_StartMarker()
{
    quint8 sourceId = 0x01;
    quint8 destId = 0x02;
    quint8 frameCounter = 0x00;
    quint8 command = 0x01;
    QByteArray params;
    
    QByteArray frame = m_handler->buildCommandFrame(sourceId, destId, frameCounter, command, params);
    
    // Start marker 0xAAAA in little-endian should be: 0xAA, 0xAA
    QCOMPARE(static_cast<quint8>(frame[0]), static_cast<quint8>(0xAA));
    QCOMPARE(static_cast<quint8>(frame[1]), static_cast<quint8>(0xAA));
    
    // Reconstruct as little-endian
    quint16 reconstructed = static_cast<quint8>(frame[0]) | (static_cast<quint8>(frame[1]) << 8);
    QCOMPARE(reconstructed, FRAME_START_MARKER);
}

// Test little-endian byte ordering for status values
void TestProtocolHandler::testLittleEndian_StatusValues()
{
    QSignalSpy spy(m_handler, &ProtocolHandler::frameReceived);
    
    // Use specific values that will show byte order issues if wrong
    qint16 currentStep = 0x1234;   // Little-endian: 0x34, 0x12
    qint16 totalTravel = 0x5678;   // Little-endian: 0x78, 0x56
    
    QByteArray frame = createStatusFrame(0x02, 0x01, 0x00, 0x01, 0x03, currentStep, totalTravel);
    
    m_handler->processIncomingData(frame);
    
    // Verify signal was emitted
    QCOMPARE(spy.count(), 1);
    
    // Verify values were correctly parsed with little-endian
    QList<QVariant> arguments = spy.takeFirst();
    QCOMPARE(arguments.at(3).value<qint16>(), currentStep);
    QCOMPARE(arguments.at(4).value<qint16>(), totalTravel);
}

QTEST_MAIN(TestProtocolHandler)
#include "test_protocolhandler.moc"
